home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Source Code / Pascal / Snippets / OffScreen Toys 1.1 / OffscreenToys.p < prev    next >
Encoding:
Text File  |  1994-05-14  |  26.4 KB  |  703 lines  |  [TEXT/PJMM]

  1. {--------- OFFSCREEN TOYS 1.1 ---------}
  2. {by Ingemar Ragnemalm 1994}
  3.  
  4. {An attempt to make a simple, small, stand-alone, compatible offscreen animation demo.}
  5. {I made this since many people want to learn the internals of animation, which packages}
  6. {like SAT (Sprite Animation Toolkit) and aren't good for, even if I had included source.}
  7. {Source code examples should be small!}
  8.  
  9. {Some good points with Offscreen Toys:}
  10. {- It's free, with full source code.}
  11. {- It's complete; no other libraries needed.}
  12. {- It's sm; r: Rect);
  13.         var
  14.             tempIconBMap, tempIconMask: BitMap;
  15.             savePort: GrafPtr;
  16.             saveDevice: GDHandle;
  17.             datasize: integer;
  18.     begin
  19.         OTGetGWorld(savePort, saveDevice);
  20.         if destWorld <> nil then
  21.             OTSetGWorld(destWorld, nil)
  22.         else
  23.             destWorld := savePort; {So that CopyMask has a GrafPtr!}
  24.         if theCicn <> nil then {If we have a cicn}
  25.             if gColorQDFlag then {We have color - then it's easy.}
  26.                 PlotCicon(r, theCicn)
  27.             else
  28. {No color: Use CopyMask.}
  29. {NOTE: This only works for 9 pixels or wider cicn's! (Old QuickDraw can't handle 1 byte wide bitmaps.)}
  30. {There is a workaround for this, but that is *really* tedious.}
  31.                 begin
  32.                     HLock(Handle(theCicn));
  33. {Make the base address pointers valid}
  34.                     with theCicn^^.iconBMap do
  35.                         datasize := rowBytes * (bounds.bottom - bounds.top);
  36.                     theCicn^^.iconBMap.baseAddr := Ptr(longint(@theCicn^^.iconMaskData[0]) + datasize); {Bitmappen måste vara giltig fört!}
  37.                     theCicn^^.iconMask.baseAddr := @theCicn^^.iconMaskData[0]; {Maskbitmappen måste också vara giltig först!}
  38. {Draw with CopyMask}
  39.                     CopyMask(theCicn^^.iconBMap, theCicn^^.iconMask, destWorld^.portBits, theCicn^^.iconBMap.bounds, theCicn^^.iconBMap.bounds, r);
  40.                     HUnLock(Handle(theCicn));
  41.                 end;
  42.         OTSetGWorld(savePort, saveDevice);
  43.     end;
  44.  
  45. {To avoid a lot of boring checks later, we have a glue for NewPtr, making it emergency}
  46. {exit on out of memory. (This is of course often not what you want, but this is a demo!)}
  47.  
  48.     function OTNewPtr (size: Longint): Ptr;
  49.     begin
  50.         OTNewPtr := NewPtrClear(size);
  51.         if MemError <> noErr then
  52.             BailOut;
  53.     end;
  54.  
  55. {OTNewGWorld: Glue to NewGWorld}
  56. {I declare offscreenGWorld as GrafPtr to save us a bunch of typecasts later (in CopyBits).}
  57. {Most parameters to NewGWorld omitted - NewGWorld is smart enough to make the defaults useable.}
  58.  
  59.     procedure OTNewGWorld (var offscreenGWorld: GrafPtr; boundsRect: Rect);
  60.         var
  61.             theDevice, oldDevice: GDHandle;
  62.             ourCMHandle: CTabHandle;
  63.             err: OsErr;
  64.  
  65.             saveGD: GDHandle;
  66.             savePort: GrafPtr;
  67.     begin
  68.         OTGetGWorld(savePort, saveGD);
  69.  
  70.         if gColorQDFlag then
  71.             begin
  72.                 if noErr <> NewGWorld(GWorldPtr(offscreenGWorld), 0, boundsRect, nil, nil, [pixelsLocked]) then
  73.                     BailOut;
  74. {We lock the offscreen pixmap so we can CopyBits and PlotCIcon to it.}
  75.                 if LockPixels(CGrafPtr(offscreenGWorld)^.portPixMap) then
  76.                     ;
  77. {Note: We should unlock it (UnlockPixels) when not animating, to avoid memory fragmentation,}
  78. {but you can bother with that later if it's a problem.}
  79.             end
  80.         else
  81.             begin
  82. {Not color - setup in b/w}
  83.                 offscreenGWorld := GrafPtr(OTnewPtr(sizeof(GrafPort)));
  84.                 OpenPort(offscreenGWorld);
  85.                 offscreenGWorld^.portRect := boundsRect;
  86.                 offscreenGWorld^.portBits.bounds := offscreenGWorld^.portRect;
  87.  
  88.                 RectRgn(offscreenGWorld^.visRgn, boundsRect);
  89.                 ClipRect(boundsRect);
  90.  
  91.                 offscreenGWorld^.portBits.rowBytes := longint(((offscreenGWorld^.portRect.right - offscreenGWorld^.portRect.left + 15) div 16) * 2);
  92.                 offscreenGWorld^.portBits.baseAddr := OTnewPtr(offscreenGWorld^.portBits.rowBytes * longint(offscreenGWorld^.portRect.bottom - offscreenGWorld^.portRect.top));
  93.             end;
  94.  
  95.         OTSetGWorld(savePort, saveGD);
  96.     end;
  97.  
  98.  
  99. {TrapAvailable from IM6-3-8}
  100.     function NumToolboxTraps: Integer;
  101.     begin
  102.         if NGetTrapAddress($A86E, ToolTrap) = NGetTrapAddress($aa6e, ToolTrap) then {_InitGraf}
  103.             NumToolboxTraps := $200
  104.         else
  105.             NumToolboxTraps := $400;
  106.     end;
  107.     function GetTrapType (theTrap: Integer): TrapType;
  108.         const
  109.             TrapMask = $800;
  110.     begin
  111.         if band(theTrap, TrapMask) > 0 then
  112.             GetTrapType := ToolTrap
  113.         else
  114.             GetTrapType := OSTrap;
  115.     end;
  116.     function TrapAvailable (theTrap: Integer): Boolean;
  117.         var
  118.             tType: TrapType;
  119.     begin
  120.         tType := GetTrapType(theTrap);
  121.         if tType = ToolTrap then
  122.             begin
  123.                 theTrap := band(theTrap, $7ff);
  124.                 if theTrap >= NumToolboxTraps then
  125.                     theTrap := $A89F;{_Unimplemented}
  126.             end;
  127.         TrapAvailable := NGetTrapAddress(theTrap, tType) <> NGetTrapAddress($A89F, ToolTrap);{_Unimplemented}
  128.     end;
  129. {End of code from IM6}
  130.  
  131. { --- PART 3: Application specific routines: ---------------------------------}
  132.  
  133. {mouse clicks, keydowns, background tasks and update events: This is where all}
  134. {the action is. :-) I include some empty procedures for you to fill in if you want to}
  135. {use this demo as application shell.}
  136.  
  137. {Mouse click in window content}
  138.  
  139.     procedure DoMouse (where: Point; modifiers: Longint);
  140.     begin
  141.     end;
  142.  
  143. {Keydown.}
  144.  
  145.     procedure DoKey (theKey: Char; modifiers: Longint);
  146.     begin
  147.     end;
  148.  
  149. {DoBackground: repeating tasks - this is called repeatedly, after every event we get.}
  150.  
  151. {Note: If you are making a really Mac-friendly program, this is where you should drive}
  152. {the animation. However, it is hard to get high framerate then, since other programs}
  153. {(the Finder included) will process events which will make it less smooth.}
  154.  
  155.     procedure DoBackground;
  156.         const
  157.             kWallBounce = 7;                            {1/10-ths of speed kept after wallbounce}
  158.             kBallDiameterSquared = 32 * 32;            {Diameter 32, squared}
  159.         var
  160.             tmpRect: Rect;
  161.             i, j: integer;
  162.             vector: Point;
  163.             saveGD: GDHandle;
  164.             savePort: GrafPtr;
  165.             tmpSpeed: Point;
  166.             squaredLength: Longint;
  167.             p1, p2, n1, n2: Point;
  168.  
  169. {Split a vector (v1) into one component parallell to another vector (direction) and one}
  170. {orthogonal to it.}
  171.         procedure SplitVector (v1, direction: Point; var parallell, normal: Point);
  172.             var
  173.                 l2, v1pr: Longint;
  174.         begin
  175. {parallell := direction * (v1 DOT direction) /|direction|**2}
  176. {normal := v1 - parallell}
  177.  
  178.             l2 := direction.h * direction.h + direction.v * direction.v; {Squared length of "direction"}
  179.             v1pr := v1.h * direction.h + v1.v * direction.v; {Scalar product}
  180.  
  181.             parallell.h := direction.h * v1pr div l2;
  182.             parallell.v := direction.v * v1pr div l2;
  183.             normal.h := v1.h - parallell.h;
  184.             normal.v := v1.v - parallell.v;
  185.         end;
  186.  
  187. {A rather boring subroutine that moves the sprites i and j away from each other.}
  188. {I almost didn't want to put this in, making the demo unnecessarily big, but I wanted}
  189. {decent collisions. If anyone can suggest a better (simpler) collision handling for circular}
  190. {objects, I'd be happy to put it in.}
  191. {I use a line drawing algorithm for it. I'm sure there are better ways. This is of questionable}
  192. {value as reuseable code - depends on your application.}
  193.         procedure Separate (i, j: integer);
  194.             var
  195.                 initVector, nowVector: Point;
  196.                 absH, absV: integer;
  197.                 moveH, moveV: integer;
  198.                 frac: integer;
  199. {Normal signum function (which I don't think is in the libs)}
  200.             function Sgn (x: integer): integer;
  201.             begin
  202.                 if x > 0 then
  203.                     Sgn := 1
  204.                 else if x < 0 then
  205.                     Sgn := -1
  206.                 else
  207.                     Sgn := 0;
  208.             end;
  209.  
  210.         begin {Separate}
  211.             initVector.h := position[i].h - position[j].h;
  212.             initVector.v := position[i].v - position[j].v;
  213.             absH := abs(initVector.h);
  214.             absV := abs(initVector.v);
  215.             moveH := Sgn(initVector.h);
  216.             moveV := Sgn(initVector.v);
  217.             if moveH = 0 then
  218.                 if moveV = 0 then
  219.                     moveV := 1;
  220.             repeat
  221.                 if absH > absV then
  222.                     begin
  223.                         position[i].h := position[i].h + moveH;
  224.                         position[j].h := position[j].h - moveH;
  225.                         frac := frac + absV;
  226.                         if frac > absH then
  227.                             begin
  228.                                 position[i].v := position[i].v + moveV;
  229.                                 position[j].v := position[j].v - moveV;
  230.                                 frac := frac - absH;
  231.                             end
  232.                     end
  233.                 else
  234.                     begin
  235.                         position[i].v := position[i].v + moveV;
  236.                         position[j].v := position[j].v - moveV;
  237.                         frac := frac + absH;
  238.                         if frac > absV then
  239.                             begin
  240.                                 position[i].h := position[i].h + moveH;
  241.                                 position[j].h := position[j].h - moveH;
  242.                                 frac := frac - absV;
  243.                             end
  244.                     end;
  245.                 nowVector.h := position[i].h - position[j].h;
  246.                 nowVector.v := position[i].v - position[j].v;
  247.             until longint(nowVector.h) * nowVector.h + longint(nowVector.v) * nowVector.v > kBallDiameterSquared;
  248.             fixedPos[i].h := BSL(position[i].h, 4); {Make fixedPos by shifting in the 4 binary "decimals"}
  249.             fixedPos[i].v := BSL(position[i].v, 4);
  250.         end;
  251.  
  252.     begin {DoBackground}
  253.         OTGetGWorld(savePort, saveGD);
  254. {1: Erase all sprites from offScreen}
  255. {Note: We keep the rectangles r[i] for erasing on the screen, later.}
  256.         OTSetGWorld(offScreen, nil);
  257.         for i := 1 to kSpriteNumber do
  258.             begin
  259.                 r[i] := gCicn^^.iconBMap.bounds;
  260.                 OffsetRect(r[i], position[i].h, position[i].v);
  261.                 CopyBits(backScreen^.portBits, offScreen^.portBits, r[i], r[i], srcCopy, nil);
  262.             end;
  263. {2: Change the position and speed}
  264.         for i := 1 to kSpriteNumber do
  265.             begin
  266.                 fixedPos[i].h := fixedPos[i].h + speed[i].h; {Modify fixed-point position by speed}
  267.                 fixedPos[i].v := fixedPos[i].v + speed[i].v;
  268.                 position[i].h := BSR(fixedPos[i].h, 4); {Make position by shifting away the 4 binary "decimals"}
  269.                 position[i].v := BSR(fixedPos[i].v, 4);
  270.                 if fixedPos[i].h + speed[i].h < 0 then
  271.                     speed[i].h := abs(speed[i].h) * kWallBounce div 10 + 1;
  272.                 if fixedPos[i].v + speed[i].v < 0 then
  273.                     speed[i].v := abs(speed[i].v) * kWallBounce div 10 + 1;
  274.                 if position[i].h + gCicn^^.iconBMap.bounds.right > offScreen^.portRect.right then
  275.                     speed[i].h := -abs(speed[i].h) * kWallBounce div 10 - 1;
  276.                 if position[i].v + gCicn^^.iconBMap.bounds.bottom > offScreen^.portRect.bottom then
  277.                     speed[i].v := -abs(speed[i].v) * kWallBounce div 10 - 1;
  278.  
  279. {Are we in the bowl? If we are, accelerate towards the center.}
  280.                 vector.h := position[i].h + 16 - BSR(offScreen^.portRect.right, 1);
  281.                 vector.v := position[i].v + 16 - BSR(offScreen^.portRect.bottom, 1);
  282.                 if (vector.h * vector.h + vector.v * vector.v) < gBowlSize then
  283.                     begin
  284.                         speed[i].h := speed[i].h - vector.h div 2;
  285.                         speed[i].v := speed[i].v - vector.v div 2;
  286.                     end;
  287.             end; {position/speed loop}
  288.  
  289. {Check for collisions}
  290.         if gCollisionFlag then
  291.             for i := 1 to kSpriteNumber - 1 do {For all objects except the last}
  292.                 for j := i + 1 to kSpriteNumber do {compare its position to all following objects}
  293.                     begin
  294. {Find the vector between them}
  295.                         vector.h := position[i].h - position[j].h;
  296.                         vector.v := position[i].v - position[j].v;
  297.                         squaredLength := longint(vector.h) * vector.h + longint(vector.v) * vector.v;
  298. {If it is shorter than the diameter of a ball…}
  299.                         if squaredLength < kBallDiameterSquared then
  300.                             begin
  301. {Move them away from each other}
  302.                                 Separate(i, j);
  303. {Swap the speed components that are parallell to "vector" (this allows for "touches", very}
  304. {nice and realistic bounces)}
  305.                                 SplitVector(speed[i], vector, p1, n1);
  306.                                 SplitVector(speed[j], vector, p2, n2);
  307.  
  308.                                 speed[j].h := p1.h + n2.h;
  309.                                 speed[j].v := p1.v + n2.v;
  310.  
  311.                                 speed[i].h := p2.h + n1.h;
  312.                                 speed[i].v := p2.v + n1.v;
  313.  
  314. {Old Offscren Toys just swapped the speed, as commented out below. This is not as realistic.}
  315. {tmpSpeed := speed[i];}
  316. {speed[i] := speed[j];}
  317. {speed[j] := tmpSpeed;}
  318.  
  319. {Play a sound.}
  320. {Note: This is not a sound demo, so I'm just using the simplest synchronous sound playing method,}
  321. {not even checking for errors in GetNamedResource. Real programs use bufferCmd and asynchronous}
  322. {sound. Then we could use longer sounds without interrupting the animation and synch it better to}
  323. {the events they illustrate.}
  324.                                 if false then
  325.                                     if gSoundFlag then
  326.                                         if SndPlay(nil, GetNamedResource('snd ', 'Kgck'), false) <> noErr then
  327.                                             ; {Ignore error}
  328.                             end;
  329.                     end; {collision loop}
  330.  
  331. {3: Draw sprites in offScreen}
  332. {Note: PlotCIcon (OTPlotCicn) is not very fast! We can speed it up my pre-drawing them in some}
  333. {offscreen, and CopyBits them from there.}
  334.         for i := 1 to kSpriteNumber do
  335.             begin
  336.                 tmpRect := gCicn^^.iconBMap.bounds;
  337.                 OffsetRect(tmpRect, position[i].h, position[i].v);
  338.                 OTPlotCicn(gCicn, offScreen, tmpRect);
  339. {FrameOval(tmpRect); {SÅ LÄNGE…}
  340.             end;
  341.  
  342. {4: Copy sprites to the screen (gWind) - both old and new position!}
  343. {Note: Depending on what limitations we have on movement, we may be able to avoid the multiple}
  344. {CopyBitsing here. E.g. if sprites always move a maximum of 2 pixels, we can copy a 2 pixels}
  345. {larger area, etc.}
  346.         OTSetGWorld(gWind, saveGD); {Vilken GD???}
  347.         for i := 1 to kSpriteNumber do
  348.             begin
  349.                 CopyBits(offScreen^.portBits, gWind^.portBits, r[i], r[i], srcCopy, nil);
  350.                 r[i] := gCicn^^.iconBMap.bounds;
  351.                 OffsetRect(r[i], position[i].h, position[i].v);
  352.                 CopyBits(offScreen^.portBits, gWind^.portBits, r[i], r[i], srcCopy, nil);
  353.             end;
  354.         OTSetGWorld(savePort, saveGD);
  355.     end; {DoBackground}
  356.  
  357. {DoUpdate: handle update events, in this case by copying offScreen to the screen (gWind).}
  358.  
  359. {Note to beginners: A program without update events processing is not a real Mac program!}
  360. {All drawing you do must reach the update event handler in some way, or it might be lost,}
  361. {or worse, partially erased, which is really ugly.}
  362.  
  363.     procedure DoUpdate;
  364.         var
  365.             saveGD: GDHandle;
  366.             savePort: GrafPtr;
  367.     begin
  368.         OTGetGWorld(savePort, saveGD);
  369.         SetPort(gWind); {or OTSetGWorld(gWind, GetMainDevice), in case I forget to et back the device?}
  370.         BeginUpdate(gWind);
  371. {Do drawing here - in this case a CopyBits}
  372.         CopyBits(offScreen^.portBits, gwind^.portBits, gwind^.portRect, gwind^.portRect, srcCopy, nil);
  373.         EndUpdate(gWind);
  374.         OTSetGWorld(savePort, saveGD);
  375.     end;
  376.  
  377. {DoAppleMenu and DoFileMenu: handle menu selections}
  378.  
  379.     procedure DoAppleMenu (item: integer);
  380.         var
  381.             str: Str255;
  382.             h: Handle;
  383.             saveGD: GDHandle;
  384.             savePort: GrafPtr;
  385.             ignore: integer;
  386.     begin
  387.         if item = 1 then
  388.             begin
  389.                 if Alert(kAboutAlertID, nil) = 1 then
  390.                     ; {Ignore result}
  391.             end
  392.         else
  393. {Apple menu other than "About": Code from TransSkel}
  394.             begin
  395.                 OTGetGWorld(savePort, saveGD); {I guess GetPort would be ok}
  396.                 GetItem(appleMenu, item, str);
  397.                 SetResLoad(false);
  398.                 h := GetNamedResource('DRVR', str);
  399.                 SetResLoad(true);
  400.                 if h <> nil then
  401.                     begin
  402.                         ResrvMem(SizeResource(h) + $1000);
  403.                         ignore := OpenDeskAcc(str);
  404.                     end;
  405.                 OTSetGWorld(savePort, saveGD);
  406.             end;
  407.     end; {DoAppleMenu}
  408.  
  409.     procedure DoFileMenu (item: integer);
  410.     begin
  411.         case item of
  412.             1:
  413. {Run animation without event processing until the user clicks the mouse}
  414. {Note: This runs the animation at maximum speed. In real programs, we}
  415. {must limit the speed with the system clock, e.g. inspect TickCount.}
  416.                 while not Button do
  417.                     DoBackground;
  418.             2: 
  419.                 begin
  420.                     gCollisionFlag := not gCollisionFlag;
  421.                     CheckItem(fileMenu, 2, gCollisionFlag);
  422.                 end;
  423. {Set the flag that tells the program to quit.}
  424.             4: 
  425.                 gWhoa := true;
  426.         end; {case}
  427.     end; {DoFileMenu}
  428.  
  429. { --- PART 4: Event processing: -----------------------------------------}
  430.  
  431. {MenuSelection: Menu selection by mouse or command-key:}
  432.  
  433.     procedure MenuSelection (whatSelection: longInt);
  434.     begin
  435.         case HiWord(whatSelection) of
  436.             kAppleID: 
  437.                 DoAppleMenu(LoWord(whatSelection));
  438.             kFileID: 
  439.                 DoFileMenu(LoWord(whatSelection));
  440.         end; {case}
  441.         HiLiteMenu(0);
  442.     end;
  443.  
  444. {MainLoop: get and process events. This is the boring standard part of all programs. I prefer}
  445. {using TransSkel to get rid of it. I don't here since I want this code to be stand-alone.}
  446.  
  447.     procedure MainLoop;
  448.         const
  449.             kSleep = 5; {Real programs may modify the sleep time depending on whether or not they are in the front}
  450.         var
  451.             hasEvent: Boolean;
  452.             theEvent: EventRecord;
  453.             theKey: Char;
  454.             whatSelection: Longint;
  455.             whichPart: integer;
  456.             whichWindow: WindowPtr;
  457.             r: rect;
  458.     begin
  459. {Get the next event. Use WaitNextEvent if possible.}
  460.         if gHasWNE then
  461.             hasEvent := WaitNextEvent(everyEvent, theEvent, kSleep, nil)
  462.         else
  463.             begin
  464.                 SystemTask;
  465.                 hasEvent := GetNextEvent(everyEvent, theEvent);
  466.             end;
  467.  
  468. {OK, so what happened then?}
  469.         if hasEvent then
  470.             case theEvent.what of
  471.                 mouseDown: 
  472.                     begin
  473.                         whichPart := FindWindow(theEvent.where, whichWindow);
  474.                         case whichPart of
  475.                             inMenuBar: 
  476.                                 begin
  477.                                     whatSelection := MenuSelect(theEvent.where);
  478.                                     MenuSelection(whatSelection);
  479.                                 end;
  480.                             inSysWindow: 
  481.                                 SystemClick(theEvent, whichWindow);
  482.                             inGoAway: 
  483.                                 if (TrackGoAway(whichWindow, theEvent.where)) then
  484.                                     gWhoa := true;
  485.                             inDrag: 
  486.                                 begin
  487.                                     if (whichWindow <> FrontWindow) and (BitAnd(theEvent.modifiers, cmdKey) = 0) then
  488.                                         SelectWindow(whichWindow);
  489.                                     r := screenBits.bounds;            {How big is the screen? (Note: Don't use screenBits for other things: it isn't a valid BitMap any more!)}
  490.                                     r.top := r.top + kMBarHeight;        { Skip down past menu bar    }
  491.                                     InsetRect(r, 4, 4);
  492.                                     DragWindow(whichWindow, theEvent.where, r);
  493.                                 end;
  494.                             inGrow: 
  495.                                 ;  {Ignored - we don't resize}
  496.                             inContent: 
  497.                                 if (whichWindow <> FrontWindow) then
  498.                                     SelectWindow(whichWindow)
  499.                                 else
  500.                                     DoMouse(theEvent.where, theEvent.modifiers); {Go to application-specific mouse down handling}
  501.                         end; {case whichPart}
  502.                     end; {mouseDown}
  503.                 keyDown, autoKey: 
  504.                     begin
  505.                         theKey := char(BitAnd(theEvent.message, charCodeMask));
  506.                         if (BitAnd(theEvent.modifiers, cmdKey) <> 0) then
  507.                             MenuSelection(MenuKey(theKey))
  508.                         else
  509.                             DoKey(theKey, theEvent.modifiers);
  510.                     end;
  511.                 updateEvt:
  512. {There's only one window to bother with here, but let's make sure that's the one the Mac wants to update.}
  513.                     if WindowPtr(theEvent.message) = gWind then
  514.                         DoUpdate;
  515. {Handle disk inserts like TransSkel.}
  516.                 diskEvt: 
  517.                     if (HiWord(theEvent.message) <> noErr) then
  518.                         begin
  519.                             DILoad;
  520.                             if DIBadMount(Point($00400040), theEvent.message) = 0 then
  521.                                 ;
  522.                             DIUnload;
  523.                         end; {diskEvt}
  524.                 otherwise {Other events are ignored}
  525.             end; {case}
  526.  
  527.         DoBackground;
  528.     end;
  529.  
  530. { --- PART 5: Initializations: -----------------------------------------}
  531.  
  532. {OTInit: Initialize global flags, menus and window}
  533.  
  534.     procedure OTInit;
  535.         const
  536. {Trap numbers}
  537.             _WaitNextEvent = $A860;
  538.             _GetCIcon = $AA1E; {E.g. any Color QuickDraw routine}
  539.             k32bQD = $AB1D;
  540.             _SndPlay = $A805;
  541.     begin
  542.         gHasWNE := TrapAvailable(_WaitNextEvent);
  543.         gColorQDFlag := TrapAvailable(k32bQD) and TrapAvailable(_GetCIcon); {???}
  544.         gWhoa := false;
  545.         gCollisionFlag := false;
  546.         gSoundFlag := TrapAvailable(_SndPlay);
  547.  
  548. {What more should I check for? Check with Gestalt instead?}
  549.  
  550.         randSeed := TickCount;            {Seed the random number generator - TickCount is good enough.}
  551.  
  552. {Get the window, a color window if we are going to use color.}
  553.         if gColorQDFlag then
  554.             gWind := GetNewCWindow(kWindId, nil, WindowPtr(-1))
  555.         else
  556.             gWind := GetNewWindow(kWindId, nil, WindowPtr(-1));
  557.  
  558. {Some menus. We could read these from resources.}
  559.         appleMenu := NewMenu(kAppleID, stringof(char($14)));
  560.         AppendMenu(appleMenu, 'About OffscreenToys…;(-');
  561.         AddResMenu(appleMenu, 'DRVR');
  562.         InsertMenu(appleMenu, 0);            { put apple menu at end of menu bar }
  563.         fileMenu := NewMenu(kFileID, 'File');
  564.         AppendMenu(fileMenu, 'Try max speed;Collisions;(-;Quit/Q');
  565.         InsertMenu(fileMenu, 0);            { put file menu at end of menu bar }
  566.         DrawMenuBar;
  567.     end;
  568.  
  569. {OTOffscreensInit: Initialize offscreen grafports (worlds) and draw in them.}
  570.  
  571.     procedure OTOffscreensInit;
  572.         var
  573.             saveGD: GDHandle;
  574.             savePort: GrafPtr;
  575.             thePat: PixPatHandle;
  576.             r: Rect;
  577.             i: integer;
  578.             colorFlag: Boolean;
  579.         const
  580.             patID = 128;
  581.  
  582. {A little routine for setting the forecolor with a single line.}
  583.         procedure OTForeColor (red, green, blue: integer);
  584.             var
  585.                 theColor: RGBColor;
  586.         begin
  587.             theColor.red := red;
  588.             theColor.green := green;
  589.             theColor.blue := blue;
  590.             RGBForeColor(theColor);
  591.         end;
  592.  
  593.     begin {OTOffscreensInit}
  594.         OTGetGWorld(savePort, saveGD);
  595.  
  596.         OTNewGWorld(offScreen, gWind^.portRect);
  597.         OTNewGWorld(backScreen, gWind^.portRect);
  598.  
  599.         OTSetGWorld(backScreen, nil);
  600.  
  601. {Do some drawing in backScreen. First, we paint a pattern (using a 'ppat' resource):}
  602.  
  603. {For drawing the background, let's make a local flag that tells us if we shold draw}
  604. {b/w patterns or color ones.}
  605.         if gColorQDFlag then
  606.             colorFlag := (CGrafPtr(backScreen)^.portPixMap^^.pixelSize > 1)
  607.         else
  608.             colorFlag := false;
  609.  
  610.         if colorFlag then
  611.             begin
  612.                 thePat := GetPixPat(patID);
  613.                 PenPixPat(thePat)
  614.             end
  615.         else
  616.             begin
  617.                 thePat := PixPatHandle(GetResource('ppat', patID));
  618.                 PenPat(thePat^^.pat1Data);
  619.             end;
  620.         PaintRect(backScreen^.portRect);
  621.         PenNormal;
  622.  
  623. {Then we draw some circles.}
  624.  
  625.         r := backScreen^.portRect;
  626.         InsetRect(r, (r.right - r.left) div 8, (r.bottom - r.top) div 8);
  627.         gBowlSize := longint(r.right - r.left) * (r.right - r.left) div 4;        {Tells how big the "bowl" is!}
  628.         if colorFlag then
  629.             begin
  630.                 OTForeColor(-10000, -10000, -10000);
  631.                 PaintOval(r);
  632.             end
  633.         else
  634.             FillOval(r, ltGray);
  635.  
  636.         InsetRect(r, (r.right - r.left) div 8, (r.bottom - r.top) div 8);
  637.         if colorFlag then
  638.             begin
  639.                 OTForeColor(-25000, -25000, -25000);
  640.                 PaintOval(r);
  641.             end
  642.         else
  643.             FillOval(r, gray);
  644.  
  645.         InsetRect(r, (r.right - r.left) div 6, (r.bottom - r.top) div 6);
  646.         if colorFlag then
  647.             begin
  648.                 OTForeColor(20000, 20000, 20000);
  649.                 PaintOval(r);
  650.             end
  651.         else
  652.             FillOval(r, dkGray);
  653.  
  654.         InsetRect(r, (r.right - r.left) div 5, (r.bottom - r.top) div 5);
  655.         if colorFlag then
  656.             begin
  657.                 OTForeColor(0, 0, 0);
  658.                 PaintOval(r);
  659.             end
  660.         else
  661.             FillOval(r, black);
  662.  
  663. {Done drawing!}
  664. {For your own hacks, consider using a PICT resource and use GetPicture and DrawPicture to draw the}
  665. {background. Note that you'll need both a color and a b/w picture if you want it to look good in b/w.}
  666.  
  667.         OTSetGWorld(offScreen, nil);
  668.         CopyBits(backScreen^.portBits, offScreen^.portBits, backScreen^.portRect, backScreen^.portRect, srcCopy, nil);
  669.  
  670.         OTSetGWorld(savePort, saveGD);
  671.  
  672. {Get the cicn resource}
  673. {Note: You can, of course, use several cicns and switch between.}
  674.         gCicn := OTGetCicn(128);
  675.  
  676. {Initialize the sprite information arrays:}
  677.  
  678.         for i := 1 to kSpriteNumber do
  679.             begin
  680.                 position[i].h := Rand(offScreen^.portRect.right - 32);
  681.                 position[i].v := (i - 1) * (offScreen^.portRect.bottom - 32) div 5 + Rand((offScreen^.portRect.bottom - 32) div 5);
  682.                 fixedPos[i].h := BSL(position[i].h, 4);
  683.                 fixedPos[i].v := BSL(position[i].v, 4);
  684.                 speed[i].h := Random mod 32;
  685.                 speed[i].v := Random mod 32;
  686.             end;
  687.     end;
  688.  
  689. { --- MAIN PROGRAM BODY: -----------------------------------------}
  690.  
  691. begin
  692.     OTInit;                    {General initializations}
  693.     OTOffscreensInit;        {Set up the offscreen grafports}
  694.     InitCursor;                {Set the cursor to arrow in case it isn't.}
  695.  
  696. {Run until quit or click in the close box.}
  697.     repeat
  698.         MainLoop;
  699.     until gWhoa;
  700.  
  701. {No cleanup is necessary here.}
  702. {We could DisposeGWorld, but that isn't necessary when we are quitting.}
  703. end.